-
-
Save Aldlevine/93b8958a39fe7ce7ad8c2b253ccec1c5 to your computer and use it in GitHub Desktop.
extends AtlasTexture | |
class_name AnimatedAtlasTexture | |
export(int, 1, 100) var h_frames := 1 | |
export(int, 1, 100) var v_frames := 1 | |
export var fps := 10.0 | |
var previous_frame := 0 | |
var frame := 0 | |
func _init() -> void: | |
var err = VisualServer.connect("frame_pre_draw", self, "_update") | |
assert(err == OK) | |
func _update() -> void: | |
if atlas: | |
previous_frame = frame | |
var img := atlas.get_data() | |
var size = img.get_size() | |
var frame_size = size / Vector2(h_frames, v_frames) | |
frame = int(int(OS.get_ticks_msec() / (1000.0 / fps)) % (h_frames * v_frames)) | |
var frame_pos = Vector2(frame % h_frames, floor(float(frame) / h_frames)) | |
region = Rect2(frame_size * frame_pos, frame_size) | |
if previous_frame != frame: | |
emit_changed() |
## Use this instead of a TileSet for any TileSet that contains AnimatedAtlasTexture. | |
## Can be added as a script to any TileSet resource, or you can create it directly in the | |
## TileMap's tile_set field by selecting "New AnimatedTileSet" instead of "New TileSet" | |
## The key is that emit_changed() must be called for the TileMap to update. | |
tool | |
extends TileSet | |
class_name AnimatedTileSet | |
var _needs_update := false | |
func _init() -> void: | |
var err | |
var tiles := get_tiles_ids() | |
for tile in tiles: | |
var tex = tile_get_texture(tile) | |
if tex is AnimatedAtlasTexture: | |
err = tex.connect("changed", self, "_set_needs_update", []) | |
assert(err == OK) | |
err = VisualServer.connect("frame_pre_draw", self, "_update", [], CONNECT_DEFERRED) | |
assert(err == OK) | |
func _set_needs_update() -> void: | |
_needs_update = true | |
func _update() -> void: | |
if _needs_update: | |
emit_changed() | |
_needs_update = false |
[plugin] | |
name="animated_atlas_texture" | |
description="Adds an animated atlas texture resource" | |
author="Aaron Levine" | |
version="" | |
script="plugin.gd" |
tool | |
extends EditorPlugin | |
var resource : AnimatedAtlasTexture | |
func _enter_tree(): | |
VisualServer.connect("frame_pre_draw", self, "_update") | |
func _exit_tree(): | |
VisualServer.disconnect("frame_pre_draw", self, "_update") | |
func handles(object: Object) -> bool: | |
if object is AnimatedAtlasTexture: | |
resource = object as AnimatedAtlasTexture | |
return object is AnimatedAtlasTexture | |
func _update() -> void: | |
if resource is AnimatedAtlasTexture: | |
if resource.atlas: | |
var img := resource.atlas.get_data() | |
var size = img.get_size() | |
var frame_size = size / Vector2(resource.h_frames, resource.v_frames) | |
var frame = int(int(OS.get_ticks_msec() / (1000.0 / resource.fps)) % (resource.h_frames * resource.v_frames)) | |
var frame_pos = Vector2(frame % resource.h_frames, floor(float(frame) / resource.h_frames)) | |
resource.region = Rect2(frame_size * frame_pos, frame_size) |
It looks like this code isn't working in a TIleMap. I built this for a different use case, so I never noticed. It looks like this likely has to do with some TileMap optimizations and the TileSet not knowing that the resource has changed. I've found a workaround for this that involves slight alterations to the AnimatedAtlasTexture code, as well as some additional code attached to the TileMap (could probably do this directly on the TileSet too, with alterations of course).
animated_atlas_texture.gd
The change here is adding some variables to track if the current frame is different from the previous frame. If it is different, call the emit_changed()
function.
extends AtlasTexture
class_name AnimatedAtlasTexture
export(int, 1, 100) var h_frames := 1
export(int, 1, 100) var v_frames := 1
export var fps := 10.0
var previous_frame := 0
var frame := 0
func _init() -> void:
var err = VisualServer.connect("frame_pre_draw", self, "_update")
assert(err == OK)
func _update() -> void:
if atlas:
previous_frame = frame
var img := atlas.get_data()
var size = img.get_size()
var frame_size = size / Vector2(h_frames, v_frames)
frame = int(int(OS.get_ticks_msec() / (1000.0 / fps)) % (h_frames * v_frames))
var frame_pos = Vector2(frame % h_frames, floor(float(frame) / h_frames))
region = Rect2(frame_size * frame_pos, frame_size)
if previous_frame != frame:
emit_changed()
custom_tile_map.gd
When added to the TIleMap, connects to each AnimatedAtlasTexture's changed
signal and calls tile_set.emit_changed()
, which informs the TileMap it needs to update.
extends TileMap
var _needs_update := false
func _ready() -> void:
var tiles := tile_set.get_tiles_ids()
for tile in tiles:
var tex = tile_set.tile_get_texture(tile)
if tex is AnimatedAtlasTexture:
tex.connect("changed", self, "_set_needs_update")
func _set_needs_update() -> void:
_needs_update = true
func _process(_delta: float) -> void:
if _needs_update:
tile_set.emit_changed()
_needs_update = false
I've updated the gist to include these changes.
Also, I didn't notice earlier, but you are using this in an AnimatedTexture, which isn't right. AnimatedAtlasTexture should replace AnimatedTexture all together (not be used as frame 0). It should animate on it's own (works out of the box with sprites and ui nodes, requires the above workaround to work with tilemaps).
Yeah, when I had it without the AnimatedTexture
, it was just invisible in the game so I was using that as a workaround.
The change definitely works; I can see it animated in the TileMap
editor now. One thing: I had to add a class_name
in the custom TileMap
script like so:
Otherwise it wouldn't show up in my resources list.
Thanks for looking into this and updating it!
In my setup, previous_frame
is reassigned in the beginning of the update function (before frame
gets rewritten), but your placement should work just the same.
Also, I've updated the solution for TileMaps by creating a custom TileSet instead. The gist was updated to include animated_tile_set.gd
. I believe this is a better solution.
And one more thing, you can get better in editor previewing by turning on "Editor Settings -> Interface -> Editor -> Update Continuously", but obviously this will make the editor more resource intensive.
Changed a lil bit, maked script straightforward + added support per AtlasTexture region (if you want make mutltiple AtlasTextures from single StreamTexture)
animated_atlas_texture.gd
class_name AnimatedAtlasTexture extends AtlasTexture
export(int, 1, 100) var h_frames := 1
export(int, 1, 100) var v_frames := 1
export var fps := 10.0
var frame_position : Vector2
var frame_size : Vector2
var frame_last := 0
var frame := 0
func init() -> void:
frame_size = region.size / Vector2(h_frames, v_frames)
frame_position = region.position
VisualServer.connect("frame_pre_draw", self, "_on_frame_pre_draw")
func _on_frame_pre_draw() -> void:
if !atlas: return
frame = int(OS.get_ticks_msec() / (1000.0 / fps)) % (h_frames * v_frames)
region = Rect2(
frame_position + (frame_size * Vector2(frame % h_frames, frame / h_frames)),
frame_size)
if frame_last != frame:
frame_last = frame
emit_changed()
tilemap.gd
extends TileMap
func _ready() -> void:
for tile in tile_set.get_tiles_ids():
var texture := tile_set.tile_get_texture(tile)
if texture is AnimatedAtlasTexture:
texture.init()
texture.connect("changed", self, "_on_AnimatedAtlasTexture_changed")
func _on_AnimatedAtlasTexture_changed() -> void:
tile_set.emit_changed()
This doesn't seem to work properly. I created a test
AnimatedAtlasTexture
(128x64) withh_frames = 2
as Frame 0 of anAnimatedTexture
.I added this to a
TileMap
:And brushed some into a scene:
If I add an output during gameplay, I can see that the region is being updated:
However, there is no animation in the actual game scene. Is my setup incorrect, or is this a bug?